/////////////////////////////////////////////////////////////////////////////
// Purpose:     Remote connections through sockets management - server
// Author:      Jesus Gonzalez
// Modified by:
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/Socket.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>

#include "Log.h"

void ServerSocket::Listen( uint16 port, int queue_size )
{
	if( m_fd >= 0 )
	{
		THROW_ERROR( "Socket is already listening" );
	}

    // TODO: socket configuration modifiers

	// Create the TCP socket
	m_fd = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
	if( m_fd < 0 )
	{
		THROW_ERROR( "Failed to create server socket" );
	}

	int sockopt = 1;
	if( setsockopt( m_fd, SOL_SOCKET, SO_REUSEADDR,
	                &sockopt, sizeof(sockopt) ) )
	{
		THROW_ERROR( "Failed to set socket options" );
	}

	// Construct address structure
	struct sockaddr_in addr;
	memset( &addr, 0, sizeof(addr) ); // Clear struct
	addr.sin_family = AF_INET; // Internet/IP
	addr.sin_addr.s_addr = htonl( INADDR_ANY ); // Incoming addr (any local)
	addr.sin_port = htons( port ); // Server port

	// Bind the server socket
	if( bind( m_fd, (struct sockaddr*)&addr, sizeof(addr) ) < 0 )
	{
		THROW_ERROR( "Failed to bind the server socket" );
	}

	// Listen on the server socket
	if( listen( m_fd, queue_size ) < 0 )
	{
		THROW_ERROR( "Failed to listen on server socket" );
	}

	LOG_DEBUG( "Server socket listening on port %d (fd = %d)", port, m_fd );
}

RemoteSocket::Ptr ServerSocket::Accept()
{
	int client_socket;
	struct sockaddr_in client_addr;
	unsigned int addr_size = sizeof(client_addr);

	// Wait for client connection
	client_socket = accept( m_fd, (struct sockaddr *) &client_addr, &addr_size );
	if( client_socket < 0 )
	{
		THROW_ERROR( "Failed to accept client connection" );
	}
	else
	{
	  LOG_DEBUG( "Remote client '%s:%d' connected (fd = %d)",
	             inet_ntoa(client_addr.sin_addr),
	             ntohs(client_addr.sin_port),
	             client_socket );
	  return new RemoteSocket( client_socket );
	}
}

bool ServerSocket::Wait( int timeout )
{
    if( m_fd < 0 )
    {
        THROW_ERROR( "Can't wait connections from a closed socket" );
    }

    struct timeval* ts_timeout = NULL;
    struct timeval _ts_timeout;
    if( timeout >= 0 )
    {
        _ts_timeout.tv_sec = (timeout / 1000);
        _ts_timeout.tv_usec = (timeout % 1000)*1000;
        ts_timeout = &_ts_timeout;
    }

    fd_set fds;
    FD_ZERO( &fds );
    FD_SET( m_fd, &fds );

    // Check if we've got a data ready
    int n = 0;
    while(true)
    {
        n = select( m_fd+1, &fds, NULL, NULL, ts_timeout );
        if( n < 0 )
        {
            if( errno == EINTR ) continue;

            THROW_ERROR( "Error waiting for data (Error=%s)", strerror(errno) );
        }

        break;
    }

    return (n > 0);
}

void ServerSocket::GetInfo( std::string &address, uint16 &port )
{
	if( m_fd < 0 )
	{
		THROW_ERROR( "Can't wait for read from a closed socket" );
	}

	struct sockaddr_in addr;
	socklen_t addr_sz = sizeof(addr);

	if( getsockname( m_fd, (struct sockaddr*) &addr, &addr_sz ) )
	{
		THROW_ERROR( "Can't get remote address (Error=%s)", strerror(errno) );
	}

	address =  inet_ntoa( addr.sin_addr );
	port = ntohs( addr.sin_port );
}

std::string ServerSocket::GetAddress()
{
	std::string address;
	uint16 port;

	GetInfo( address, port );
	return address;
}

uint16 ServerSocket::GetPort()
{
	std::string address;
	uint16 port;

	GetInfo( address, port );
	return port;
}
